// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include "CaeAgent.h"
#include "../cae_common/CaeConfigManage.h"
#include "../cae_dataPipe/CaeAudioPipe.h"
#include "../cae_dataPipe/CaeVideoPipe.h"
#include "../cae_common/CaeCommon.h"
#include "../cae_service/CaeHttpsJob.h"
#include "../cae_dataPipe/CaeControlCmdPipe.h"
#include "../cae_dataPipe/CaeVirtualDevicePipe.h"
#include "../cae_common/CaeMsgCode.h"
#include "../cae_common/CaeAppCtrlCmdUtils.h"
#include "../cae_service/CaeParamStorage.h"
#include "../cae_service/CaeMediaConfig.h"
#include "../cae_dataPipe/CaeChannelDataPipe.h"
#include "../cae_dataPipe/CaeImePipe.h"
#include "../cae_dataPipe/CaeScreenshotPipe.h"

#define ENABLE_SIG_HANDLER (0)

static const int MAX_PORT = 65535;
static const uint32_t MAX_INTERVAL = 1000;

CaeAgent &CaeAgent::GetInstance()
{
    static CaeAgent instance;
    instance.Init();
    return instance;
}

CaeAgent::CaeAgent()
{
    pthread_mutex_init(&m_statusLock, nullptr);
    SetStatus(CaeAgentStatus::CAE_AGENT_INIT);
    m_doReset = false;
    m_doRestart = false;
    m_wssServerSocket = nullptr;
    m_wssClientService = nullptr;
    m_webClientService = nullptr;
    m_webServerSocket = nullptr;
    m_tlsClientService = nullptr;
    m_tlsServerSocket = nullptr;
    m_tcpClientService = nullptr;
    m_tcpServerSocket = nullptr;
    m_serverNotice = nullptr;
    m_caeHttps = nullptr;
    m_connectionAgent = nullptr;
    m_caeHttpsJobThread = nullptr;
    m_heartbeatThread = nullptr;
    m_engineControl = nullptr;
    m_timerThread = nullptr;
    m_channelServer = nullptr;
    m_isInit = false;
    m_caePipes.clear();

    InitPipes();
}

CaeAgent::~CaeAgent() noexcept
{
    ClearPipesData();
    DestroyServerSockets();

    m_connectionAgent = nullptr;

    for (auto pipe : m_caePipes) {
        if (nullptr != pipe) {
            delete pipe;
            pipe = nullptr;
        }
    }

    this->m_caePipes.clear();

    if (m_engineControl != nullptr) {
        delete m_engineControl;
        m_engineControl = nullptr;
    }

    if (m_caeHttps != nullptr) {
        delete m_caeHttps;
        m_caeHttps = nullptr;
    }

    if (m_heartbeatThread != nullptr) {
        m_heartbeatThread->Stop();
        delete m_heartbeatThread;
        m_heartbeatThread = nullptr;
    }

    if (m_caeHttpsJobThread != nullptr) {
        m_caeHttpsJobThread->Stop();
        while (m_caeHttpsJobThread->m_threadStatus != CAE_THREAD_STATUS_EXIT) {
            usleep(MAX_INTERVAL);
        }
        delete m_caeHttpsJobThread;
        m_caeHttpsJobThread = nullptr;
    }

    if (m_timerThread != nullptr) {
        m_timerThread->Stop();
        while (m_timerThread->m_threadStatus != CAE_THREAD_STATUS_EXIT) {
            usleep(MAX_INTERVAL);
        }
        delete m_timerThread;
        m_timerThread = nullptr;
    }

    if (m_channelServer != nullptr) {
        m_channelServer->Deinit();
        delete m_channelServer;
        m_channelServer = nullptr;
    }

    pthread_mutex_destroy(&m_statusLock);
}

/**
 * 初始化CAE的数据管道
 */
void CaeAgent::InitPipes()
{
    auto *videoPipe = new (std::nothrow) CaeVideoPipe(VIDEO_PIPE, CAE_MSG_VIDEO);
    auto *audioPipe = new (std::nothrow) CaeAudioPipe(AUDIO_PIPE, CAE_MSG_AUDIO);
    auto *channelPipe = new (std::nothrow) CaeChannelDataPipe(CHANNEL_PIPE, CAE_MSG_CHANNEL);
    auto *imePipe =  new (std::nothrow) CaeImePipe(IME_PIPE, CAE_MSG_IME);
    auto *screenshotPipe =  new (std::nothrow) CaeScreenshotPipe(SCREENSHOT_PIPE, CAE_MSG_SCREENSHOT);
    CaeVirtualDevicePipe *virtualDevicePipe;
    if (CaeConfigManage::EnableVirtualDevice()) {
        virtualDevicePipe = new (std::nothrow) CaeVirtualDevicePipe(VIRTUAL_DEVICE_PIPE, CAE_MSG_VIRTUAL_DEVICE);
    }

    if (audioPipe == nullptr || videoPipe == nullptr || channelPipe == nullptr || imePipe == nullptr
        || (CaeConfigManage::EnableVirtualDevice() && virtualDevicePipe == nullptr)
        || screenshotPipe == nullptr) {
        LOGE("Failed to create pipes.");
        if (videoPipe != nullptr) {
            delete videoPipe;
            videoPipe = nullptr;
        }
        if (audioPipe != nullptr) {
            delete audioPipe;
            audioPipe = nullptr;
        }
        if (channelPipe != nullptr) {
            delete channelPipe;
            channelPipe = nullptr;
        }
        if (imePipe != nullptr) {
            delete imePipe;
            imePipe = nullptr;
        }
        if (virtualDevicePipe != nullptr) {
            delete virtualDevicePipe;
            virtualDevicePipe = nullptr;
        }
        if (screenshotPipe != nullptr) {
            delete screenshotPipe;
            screenshotPipe = nullptr;
        }
        return;
    } else {
        m_caePipes.push_back(videoPipe);
        m_caePipes.push_back(audioPipe);
        m_caePipes.push_back(channelPipe);
        m_caePipes.push_back(imePipe);
        if (CaeConfigManage::EnableVirtualDevice()) {
            m_caePipes.push_back(virtualDevicePipe);
        }
        m_screenshotPipe = screenshotPipe;
    }

    LOGI("Initializing the pipes ends.");
}

void CaeAgent::ClearPipesData() noexcept
{
    for (auto pipe : m_caePipes) {
        pipe->ClearPipeData();
    }
    LOGI("Clearing the pipes ends.");
}

void CaeAgent::OnNewClientConnectCallback(CaeSocket *socket)
{
    if (m_connectionAgent == nullptr) {
        m_connectionAgent = std::make_unique<CaeConnectionAgent>();
    }
    m_connectionAgent->AddConnector(socket);
}

void sig_exit(int sig_num)
{
    _exit(sig_num);
}

/**
 * 通过信号量-动态设置日志级别
 * @param iSignum
 */
void SIGUSR1Handler(int iSignum)
{
#if ENABLE_SIG_HANDLER
    LOGD("Reset LOG_level.");
    LOGI("Reset LOG_level.");
    LOGE("Reset LOG_level.");
    CaeConfigManage::getInstance().setNetConfig("/system/etc/cloudAppEngine/CaeConfig.ini", "CaeAgent");
    LOGD("Finish reset LOG_level.");
    LOGI("Finish reset LOG_level.");
    LOGE("Finish reset LOG_level.");
#endif
}

void InitSignals()
{
    signal(SIGINT, sig_exit);
    signal(SIGHUP, sig_exit);
    signal(SIGQUIT, sig_exit);
    signal(SIGKILL, sig_exit);
    signal(SIGTERM, sig_exit);
    signal(SIGCHLD, SIG_IGN);
    signal(SIGSTOP, sig_exit);
    signal(SIGXCPU, sig_exit);
    signal(SIGPROF, sig_exit);
    signal(SIGWINCH, SIG_IGN);
    signal(SIGIO, SIG_IGN);
    signal(SIGPWR, sig_exit);
    signal(SIGILL, sig_exit);
    signal(SIGUSR1, SIGUSR1Handler);
}

void CaeAgent::LockStatus()
{
    pthread_mutex_lock(&m_statusLock);
}

void CaeAgent::UnlockStatus()
{
    pthread_mutex_unlock(&m_statusLock);
}

int CaeAgent::Init()
{
    std::lock_guard<std::mutex> lock(m_initLock);
    if (m_isInit) {
        return 0;
    }
    if (m_caeHttps == nullptr) {
        LOGI("Create Https client.");
        m_caeHttps = new CaeHttps();
        if (m_caeHttps == nullptr) {
            LOGE("Init error, failed to new CaeHttps.");
            return -1;
        }
        int caeHttpInitRet = m_caeHttps->Init();
        if (caeHttpInitRet != 0) {
            LOGE("Init error, failed to inti CaeHttps.");
            return -1;
        }

        if (CaeConfigManage::EnableEventNotify() || CaeConfigManage::EnableHeartbeatNotify()) {
            CaeHttpsJob::GetInstance().SetPhoneId();
        }
    }

    if (m_timerThread == nullptr) {
        LOGI("Creating timerThread.");
        m_timerThread = new CaeTimerThread();
        if (m_timerThread == nullptr) {
            LOGE("Failed to create m_timerThread.");
            return -1;
        }
        bool startTimerThreadRet = m_timerThread->Start();
        if (!startTimerThreadRet) {
            LOGE("Failed to start timerThread, error(%d):%s", errno, strerror(errno));
            return -1;
        }
        LOGI("TimerThread started.");
    }

    if (m_caeHttpsJobThread == nullptr) {
        LOGI("Creating httpsJobThread.");
        m_caeHttpsJobThread = new CaeHttpsJobThread();
        if (m_caeHttpsJobThread == nullptr) {
            LOGE("Failed to create httpsJobThread.");
            return -1;
        }
        bool startHttpsJobThreadRet = m_caeHttpsJobThread->Start();
        if (!startHttpsJobThreadRet) {
            LOGE("Failed to start HttpsJobThread, error(%d):%s", errno, strerror(errno));
            return -1;
        }
        LOGI("HttpsJobThread started.");
    }

    if (CaeConfigManage::EnableHeartbeatNotify()) {
        if (m_heartbeatThread == nullptr) {
            LOGI("Creating heartbeatThread.");
            m_heartbeatThread = new CaeHeartbeatThread();
            if (m_heartbeatThread == nullptr) {
                LOGE("Failed to create heartbeatThread.");
                return -1;
            }
            bool startHeartBeatThreadRet = m_heartbeatThread->Start();
            if (!startHeartBeatThreadRet) {
                LOGE("Failed to start heartbeatThread, error(%d):%s", errno, strerror(errno));
                return -1;
            }
            LOGI("HeartbeatThread started.");
        }
    }

    if (m_engineControl == nullptr) {
        m_engineControl = new CaeEngineControl();
        if (m_engineControl == nullptr) {
            LOGE("Init failed, failed to new CaeEngineControl.");
            return -1;
        }
    }

    if (CaeConfigManage::EnableDataChannel()) {
        if (m_channelServer == nullptr) {
            m_channelServer = new(std::nothrow) CaeDataChannelServer();
            if (m_channelServer == nullptr) {
                LOGE("Create channel server failed.");
                return -1;
            }
            m_channelServer->Init();
        }
    }

    m_isInit = true;
    return 0;
}

void CaeAgent::Boot()
{
    LOGI("Starting boot.");
    m_serverNotice = new CaeServerNotice();
    if (!CreateServerSockets()) {
        LOGE("Failed to CreateServerSockets.");
        return;
    }
    InitSignals();
    while (m_caeStatus != CaeAgentStatus::CAE_AGENT_EXIT) {
        usleep(MAX_INTERVAL);
        LockStatus();
        if (m_doReset) {
            LOGI("Reset executing.");
            SetStatus(CaeAgentStatus::CAE_AGENT_INIT);
            m_connectionAgent->CloseConnector();
            m_doReset = false;
            UnlockStatus();
            continue;
        }
        if (m_doRestart) {
            LOGI("Restart executing.");
            ClearPipesData();
            DestroyServerSockets();
            if (!CreateServerSockets()) {
                LOGE("Failed to CreateServerSockets.");
                UnlockStatus();
                return;
            }
            SetStatus(CaeAgentStatus::CAE_AGENT_INIT);
            m_doRestart = false;
            m_doReset = false;
            UnlockStatus();
            continue;
        }
        UnlockStatus();
    }
    LOGI("End boot.");
}

void CaeAgent::Exit()
{
    SetStatus(CaeAgentStatus::CAE_AGENT_EXIT);
}

bool CaeAgent::Start()
{
    return true;
}

bool CaeAgent::Stop()
{
    return true;
}

void CaeAgent::Restart()
{
    SetStatus(CaeAgentStatus::CAE_AGENT_EXIT);
    m_doRestart = true;
    LOGI("Restart CAE.");
}

void CaeAgent::SetStatus(int status)
{
    this->m_caeStatus = status;
    switch (status) {
        case CaeAgentStatus::CAE_AGENT_EXIT:
            LOGI("CaeAgentStatus is %s", STATUS2STR(CAE_AGENT_EXIT));
            break;
        case CaeAgentStatus::CAE_AGENT_WAIT_RECONNECT:
            LOGI("CaeAgentStatus is %s", STATUS2STR(CAE_AGENT_WAIT_RECONNECT));
            break;
        case CaeAgentStatus::CAE_AGENT_PAUSE:
            LOGI("CaeAgentStatus is %s", STATUS2STR(CAE_AGENT_PAUSE));
            break;
        case CaeAgentStatus::CAE_AGENT_RUNING:
            LOGI("CaeAgentStatus is %s", STATUS2STR(CAE_AGENT_RUNING));
            break;
        case CaeAgentStatus::CAE_AGENT_INIT:
            LOGI("CaeAgentStatus is %s", STATUS2STR(CAE_AGENT_INIT));
            break;
        default:
            break;
    }
}

int CaeAgent::GetStatus() const
{
    return this->m_caeStatus;
}

void CaeAgent::CloseClient()
{
    m_doReset = true;
    LOGI("Closing client");
}

bool CaeAgent::CreateServerSockets()
{
    LOGI("Start creating server socket");

    uint16_t appPort;
    appPort = CaeConfigManage::GetListenPortApp();

    if (appPort <= 0 || appPort >= MAX_PORT) {
        LOGE("CreateServerSocket error, app_port invalid, app_port is %d.", appPort);
        return false;
    }

    m_tlsClientService = new CaeTcpTlsClientService();
    m_tlsServerSocket = new CaeTcpServerSocket(INADDR_ANY, appPort, m_tlsClientService);
    m_tlsServerSocket->SetEventNotice(m_serverNotice);
    m_tlsServerSocket->SetServerType(ServerType::TLS);
    m_tlsClientService->SetNewClientConnectCallback([this](CaeSocket *socket){CaeAgent::OnNewClientConnectCallback(socket);});

    uint16_t h5Port;
    h5Port = CaeConfigManage::GetListenPortH5();
    if (CaeConfigManage::EnableWebSocketServer() | CaeConfigManage::EnableWSSServer()) {
        if (h5Port <= 0 || h5Port >= MAX_PORT) {
            LOGE("CreateServerSocket error, h5_port invalid, h5_port is %d.", h5Port);
            return false;
        }
    }

    if (CaeConfigManage::EnableWSSServer()) {

        m_wssClientService = new CaeWSSClientService();
        m_wssServerSocket = new CaeTcpServerSocket(INADDR_ANY, h5Port, m_wssClientService);
        m_wssServerSocket->SetEventNotice(m_serverNotice);
        m_wssServerSocket->SetServerType(ServerType::WSS);
        m_wssClientService->SetNewClientConnectCallback([this](CaeSocket *socket){CaeAgent::OnNewClientConnectCallback(socket);});
    }

    if (CaeConfigManage::EnableWebSocketServer()) {

        m_webClientService = new CaeWebClientService();
        m_webServerSocket = new CaeTcpServerSocket(INADDR_ANY, h5Port, m_webClientService);
        m_webServerSocket->SetEventNotice(m_serverNotice);
        m_webServerSocket->SetServerType(ServerType::WS);
        m_webClientService->SetNewClientConnectCallback(
                [this](CaeSocket *socket) { CaeAgent::OnNewClientConnectCallback(socket); });
    }

#if ENABLE_TCP_SERVER
        m_tcpClientService = new CaeTcpClientService();
        m_tcpServerSocket = new CaeTcpServerSocket(INADDR_ANY, appPort + 3, m_tcpClientService);
        m_tcpServerSocket->SetEventNotice(m_serverNotice);
        m_tcpServerSocket->SetServerType(ServerType::TCP);
        m_tcpClientService->SetNewClientConnectCallback([this](CaeSocket *socket){CaeAgent::OnNewClientConnectCallback(socket);});
#endif

    LOGI("End server socket creation");
    return true;
}

void CaeAgent::DestroyServerSockets() noexcept
{
    LOGI("Start destroying server sockets");

    if (m_tcpServerSocket != nullptr) {
        delete m_tcpServerSocket;
        m_tcpServerSocket = nullptr;
    }

    if (m_tcpClientService != nullptr) {
        delete m_tcpClientService;
        m_tcpClientService = nullptr;
    }

    if (m_webServerSocket != nullptr) {
        delete m_webServerSocket;
        m_webServerSocket = nullptr;
    }

    if (m_webClientService != nullptr) {
        delete m_webClientService;
        m_webClientService = nullptr;
    }

    if (m_wssServerSocket != nullptr) {
        delete m_wssServerSocket;
        m_wssServerSocket = nullptr;
    }

    if (m_wssClientService != nullptr) {
        delete m_wssClientService;
        m_wssClientService = nullptr;
    }

    if (m_tlsServerSocket != nullptr) {
        delete m_tlsServerSocket;
        m_tlsServerSocket = nullptr;
    }

    if (m_tlsClientService != nullptr) {
        delete m_tlsClientService;
        m_tlsClientService = nullptr;
    }
    if (m_serverNotice != nullptr) {
        delete m_serverNotice;
        m_serverNotice = nullptr;
    }
    LOGI("End Destroying Server Sockets");
}

int CaeAgent::SendCustomDataToClient(const std::shared_ptr<uint8_t>& data, int size, uint8_t type)
{
    if (m_connectionAgent == nullptr) {
        return -1;
    }
    return m_connectionAgent->SendCustomDataToClient(data, size, type);
}

bool CaeAgent::CheckClearCmdCondition() const
{
    if (m_caeStatus == CAE_AGENT_PAUSE || m_caeStatus == CAE_AGENT_WAIT_RECONNECT) {
        return false;
    } else {
        return true;
    }
}

CaeDataPipe* CaeAgent::GetDataPipe(int type)
{
    for (auto pipe : m_caePipes) {
        if (nullptr != pipe && pipe->m_type == type) {
            return pipe;
        }
    }
    return nullptr;
}

bool CaeAgent::IsIpInBlacklist(const string& ip)
{
    for (const string& blackIP : m_ipBlackList) {
        if (ip == blackIP) {
            return true;
        }
    }
    return false;
}

void CaeAgent::AddIpToBlacklist(const string& ip)
{
    if (ip.empty() || IsIpInBlacklist(ip)) {
        return;
    }
    LOGE("Add Ip: %s to blacklist", ip.c_str());
    m_ipBlackList.push_back(ip);
}

long CaeAgent::GetLastRequestPublicKeyTimestamp() const
{
    return m_lastRequestPublicKeyTimestamp;
}

void CaeAgent::SetLastRequestPublicKeyTimestamp(long lastRequestPublicKeyTimestamp)
{
    LOGE("m_lastRequestPublicKeyTimestamp : %ld", m_lastRequestPublicKeyTimestamp);
    m_lastRequestPublicKeyTimestamp = lastRequestPublicKeyTimestamp;
}

void CaeAgent::StartCloudPhoneIme()
{
    std::string startCmd = "am start-foreground-service com.huawei.cloudphoneime/.CaeImeService";
    std::string sdkVersion = CaeConfigManage::GetSDKVersion();
    try {
        if (!sdkVersion.empty() && stoi(sdkVersion) == ANDROID_7_1_1_SDK_VERSION) {
            startCmd = "am startservice com.huawei.cloudphoneime/.CaeImeService";
        }
    } catch (const std::invalid_argument& e) {
        LOGE("Failed to get sdkVersion");
    } catch (const std::out_of_range& e) {
        LOGE("sdkVersion is out of range.");
    }
    int tryCount = 1;
    int ret;
    do {
        ret = system(startCmd.c_str());
        if (ret == 0) {
            LOGI("Succeed to start cloud phone ime.");
            return;
        }
        LOGE("Failed to start cloud phone ime, retry times: %d, ret: %d", tryCount, ret);
        usleep(100000);
        tryCount++;
    } while (tryCount <= 3);
    LOGE("Failed to start cloud phone ime finally.");
}

void CaeAgent::ClearScreenshotPipesData() noexcept {
    m_screenshotPipe->ClearPipeData();
    LOGI("Clearing m_screenshotPipe ends.");
}

CaeDataPipe *CaeAgent::GetScreenshotDataPipe() {
    return m_screenshotPipe;
}

void CaeAgent::AddVerifiedConnectionId(const string& ip, const string& port)
{
    std::lock_guard<std::mutex> lock(m_whiteListLock);
    m_ipWhiteList[ip].insert(port);
    LOGI("Add %s:%s to White List successfully.", ip.c_str(), port.c_str());
}

void CaeAgent::DeleteVerifiedConnectionId(const string& ip, const string& port)
{
    std::lock_guard<std::mutex> lock(m_whiteListLock);
    auto it = m_ipWhiteList.find(ip);
    if (it != m_ipWhiteList.end()) {
        if (it->second.erase(port) != 0) {
            LOGI("Delete %s:%s from White List successfully.", ip.c_str(), port.c_str());
        }
        if (it->second.empty()) {
            m_ipWhiteList.erase(ip);
        }
    }
}

bool CaeAgent::IsVerifiedIp(const string& ip)
{
    std::lock_guard<std::mutex> lock(m_whiteListLock);
    if(!m_ipWhiteList.count(ip)) {
        return false;
    }
    return true;
}